package com.chunuo.configuration.proxy;

import com.chunuo.configuration.annotation.*;
import com.chunuo.configuration.data.DataQuery;
import com.chunuo.configuration.exception.ChunuoException;
import com.chunuo.configuration.interfaces.DataVisit;
import com.chunuo.configuration.utils.AnnotationUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * DAO动态代理
 *
 * @author liujiamiao
 * @email 2544296058@qq.com
 * @date 2020-12-22 16:17
 */
public class DaoProxy {

    private Method[] methods = null;

    public DaoProxy(){
        methods = DataVisit.class.getDeclaredMethods();
    }

    DataQuery query = new DataQuery();

    public <T> T getDaoProxy(Class clazz) {
        Object obj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //这个方法返回的是动态代理的地址
                if ("toString".equals(method.getName())) {
                    return this.toString();
                }

                //如果是DataVisit的方法，则不进行处理
                for(int i = 0;i<methods.length;i++){
                    if(method.equals(methods[i])){
                        //返回该方法由哪个对象执行：本对象（this），参数：arg
                        return method.invoke(query, args);
                    }
                }

                String[] methodField = method.toString().split(" ");
                if (methodField.length >= 4) {//如果是接口自定方法（public abstance 返回值 方法名，至少有4段

                    //查询数据可能包含List<xxx> 这时候就得获取返回类型的泛型是什么了
                    Type type = method.getGenericReturnType();
                    if (type instanceof ParameterizedType) {
                        type = ((ParameterizedType) type).getActualTypeArguments()[0];// 泛型类型列表
                    }

                    String sql = null;
                    //如果sql串中包含#{xx}的话，则需要传入每个获取到的属性值
                    List<Object> list = new ArrayList<>();

                    //获取方法上注解的sql
                    if (method.isAnnotationPresent(SelectForeach.class)) {//得到一组集合
                        if ((sql = method.getAnnotation(SelectForeach.class).value()).indexOf("#") != -1)
                            sql = subSql(sql, list,method, args);
                        if(sql.indexOf("*")!=-1){
                            sql = hump(sql,type);
                        }
                        return query.queryForeach(sql, Class.forName(type.toString().split(" ")[1]), list.toArray().length != 0 ? list.toArray() : args);
                    } else if (method.isAnnotationPresent(SelectOne.class)) {//得到一行数据
                        if ((sql = method.getAnnotation(SelectOne.class).value()).indexOf("#") != -1)
                            sql = subSql(sql, list,method, args);
                        if(sql.indexOf("*")!=-1){
                            sql = hump(sql,type);
                        }
                        return query.queryRow(sql, Class.forName(type.toString().split(" ")[1]), list.toArray().length != 0 ? list.toArray() : args);
                    } else if (method.isAnnotationPresent(Single.class)) {//得到一个数据
                        if ((sql = method.getAnnotation(Single.class).value()).indexOf("#") != -1)
                            sql = subSql(sql, list,method, args);
                        return query.getSingle(sql, list.toArray().length != 0 ? list.toArray() : args);
                    } else if (method.isAnnotationPresent(Update.class)) {//增删改
                        if ((sql = method.getAnnotation(Update.class).value()).indexOf("#") != -1)
                            sql = subSql(sql, list,method, args);
                        return query.update(sql, list.toArray().length != 0 ? list.toArray() : args);
                    }

                }

                return null;
            }
        });
        return (T) obj;
    }

    /**
     * 修改sql语句，以及把args中（对象）改为list跟参数一一对应
     * @param sql
     * @param list
     * @param args
     * @return
     */
    public String subSql(String sql, List<Object> list,Method method, Object[] args) throws NoSuchFieldException, IllegalAccessException {
        //拼接的sql语句
        StringBuffer sb = new StringBuffer();
        //开始端
        int begin = 0;
        //结束端
        int end = 0;
        //查看有多少个参数占位符
        int len = 0;
        while((end = sql.indexOf("#{",begin))!=-1){
            begin = end+"#{".length();
            len++;
        }

        Map argsMap = new HashMap();
        //存储@Param注解上的参数名
        if((len==1&&args.length==1)||(len>1&&len == args.length)){
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            for(int i = 0;i<parameterAnnotations.length;i++){
                for(Annotation annotation: parameterAnnotations[i]){
                    if(annotation.toString().indexOf(Param.class.toString().split(" ")[1])>=0){
                        String annotationName = AnnotationUtils.getAnnotationName(annotation);
                        argsMap.put(annotationName,args[i]);
                    }
                }
            }
        }
        System.out.println("argsMap = "+argsMap);

        //存储动态sql
        Map<String,String> compareMap = new HashMap<>();

        //动态sql
        begin=0;
        end=0;
        while((end = sql.indexOf("<if",begin))!=-1){
            int temp = end +"<if".length();
            //查看是否有test
            int left = indexOf(sql,temp,"test");
            //找到test的内容
            left = next(sql, left + "test".length());
            int right = indexOf(sql,left + 1,">");
            String content = sql.substring(left + 1, right).trim();
            System.out.println(content);
            //存两个字段，属性名和属性对比值  username!=null
            String[] fields = null;
            String centerField = null;
            if(content.indexOf("==")>=0){
                fields = content.split("==");
                centerField = "==";
            }else if(content.indexOf("!=")>=0){
                fields = content.split("!=");
                centerField = "!=";
            }else{
                throw new ChunuoException("动态占位符却没有 == 或者 != ,请检查！");
            }
            // 结构为xxx==xxx 或者 xxx!=xxx
            if(fields.length!=2){
                throw new ChunuoException("动态sql有误");
            }

            //方法参数可能传null
            Object value = argsMap.get(fields[0].trim());
            if(value==null){
                System.out.println("没有找到该 "+fields[0]+" 参数");
            }else if(String.valueOf(value).isEmpty()){
                value = null;
            }

            String compareValue = fields[1].trim();// 比如 == null
            if(compareValue.equals("null")){
                compareValue = null;
            }

            //比较结果
            boolean flag = false;
            if(centerField.equals("==")){
                //如果参数是null
                if(value==null){
                    //参数是否与动态sql的比较值一致
                    if(value==compareValue){
                        flag = true;
                    }else{
                        flag = false;
                    }
                }else{
                    //参数不为null
                    if(value.equals(compareValue)){
                        //比较值一致
                        flag = true;
                    }else{
                        flag = false;
                    }
                }
            }else if(centerField.equals("!=")){
                //参数为null
                if(value==null){
                    //如果参数和比较值不相等，那么一定不相等   (比较值要么 null，要么有值）
                    if(value!=compareValue){
                        flag = true;
                    }else{
                        flag = false;
                    }
                }else {
                    if(!value.equals(compareValue)) {
                        flag = true;
                    }else{
                        flag = false;
                    }
                }
            }

            //为真，需要拼串
            left = indexOf(sql,right,">");
            right = indexOf(sql,left,"</if>");
            String tempSql = sql.substring(left + 1, right);
            //if里面的代码
            System.out.println("tempSql = "+tempSql);
            begin = right+"</if>".length();
            String conditionSql = sql.substring(end, begin);
            //动态sql语句 ,如：<if test="xxxx==xxx">xxx</if>
            System.out.println("conditionSql = "+conditionSql);

            //找到</if>后面的<else>如果没有找到，则不用截取后面一段
            int leftElse = begin;
            leftElse = next(sql,leftElse);

            int tempLeftElse = sql.indexOf("<else>",leftElse);
            String tempSqlElse = "";
            if(tempLeftElse==-1||tempLeftElse!=leftElse){
                System.out.println("没有后半段");
                //直接保存动态sql的参数
                compareMap.put(conditionSql,tempSql);
            }else{
                //再找到</else>
                int rightElse = indexOf(sql,leftElse,"</else>");
                tempSqlElse = sql.substring(leftElse+"<else>".length(), rightElse);
                System.out.println(tempSqlElse);

                //如：<if test="xxxx==xxx">xxx</if><else>xxxx</else>
                conditionSql = sql.substring(end,rightElse+"</else>".length());
            }
            //可以保存动态sql了
            if(flag){
                compareMap.put(conditionSql,tempSql);
            }else{
                compareMap.put(conditionSql,tempSqlElse);
            }

        }



        for(Map.Entry<String,String> entry:compareMap.entrySet()){
            sql = sql.replace(entry.getKey(), entry.getValue());
        }

        //遍历原先的sql语句，直到#{xxx}替换成?
        begin = 0;
        end = 0;
        while((end = sql.indexOf("#{",begin))!=-1){

            if(args.length==0){
                throw new ChunuoException("sql串有误，没有参数却有参数占位符！");
            }else if(args.length==1&&len>1){
                //找到#{xxx}  -- > xxx
                String fieldName = sql.substring(end + "#{".length(), sql.indexOf("}", end + "#{".length()));
                //找到对象中的xxx属性
                Field field = args[0].getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                //获取xxx属性的值
                Object o = field.get(args[0]);
                //添加到list中，后续作为参数传入
                list.add(o);
                //拼接上每一段的值
                sb.append(sql.substring(begin,end)+"?");
                //+3是因为#{}总3位
                begin = end + fieldName.length()+3;
            }else{
                //找到#{xxx}  -- > xxx
                String fieldName = sql.substring(end + "#{".length(), sql.indexOf("}", end + "#{".length()));
                Object value = argsMap.get(fieldName);
                list.add(value);
                //拼接上每一段的值 如 xxxxx xx = #{xx} =>  xxxxx xx = ?
                sb.append(sql.substring(begin,end)+"?");
                //+3是因为#{}总3位
                begin = end + fieldName.length()+3;
            }
        }
        //拼接上最后一段
        sb.append(sql.substring(begin));
        System.out.println("sql = "+sb.toString());


        return sb.toString();
    }

    public int indexOf(String sql,int index,String content){
        int left = sql.indexOf(content, index);
        if(left==-1){
            throw new ChunuoException("sql结构有误");
        }
        return left;
    }


    /**
     * 找到下一个字符串
     * @param sql
     * @param off
     * @return
     */
    public int next(String sql,int off){
        for(int i = off;i<sql.length();i++){
            if(sql.charAt(i)!=' '){
                return i;
            }
        }
        return -1;
    }


    /**
     * 拆分字符串
     * @param content
     * @return
     */
    public List<String> split(String content){
        String[] split = content.split(" ");
        List<String> collect = Arrays.asList(split).stream().filter(entity -> {
            return !entity.isEmpty();
        }).collect(Collectors.toList());
        System.out.println(collect);
        return collect;
    }


    /**
     * 查询的时候，把“*”给换成mysql字段 as 实体类字段  （驼峰命名法）
     * @param sql
     * @param type
     * @return
     */
    private String hump(String sql,Type type) throws ClassNotFoundException {
        int index = sql.indexOf("*");
        StringBuffer sb = new StringBuffer(sql.substring(0,index));
        Field[] fields = Class.forName(type.toString().split(" ")[1]).getDeclaredFields();
        boolean flag = false;
        for(Field field : fields) {
            TableId tableId = field.getAnnotation(TableId.class);
            TableField tableField = field.getAnnotation(TableField.class);
            if (field.toString().indexOf(" static ") != -1 ||
                field.toString().indexOf(" final ") != -1 ||
                (tableId==null&&tableField==null)) {
                continue;
            }
            //拿到注解上的名字，可能为空
            String value = null;
            if(tableField!=null){
                value = tableField.value();
            }
            if(value!=null&&!value.isEmpty()){
                //使用注解value的字段
                sb.append((flag?" , ":" ")+value+" as "+field.getName()+" ");
            }else{
                //得到属性名
                String fieldName = AnnotationUtils.hump(field.getName());
                sb.append((flag?" , ":" ")+fieldName+" as "+field.getName()+" ");
            }
            flag = true;
        }
        sb.append(sql.substring(index+1));
        System.out.println("驼峰命名法sql = "+sb);
        return sb.toString();
    }

}
